package com.atguigu.juc.pool;

import java.util.concurrent.*;

/**
 * 1、为什么需要使用线程池？
 *      [1]对象复用
 *          对象如果不能进行复用，就意味着每次需要使用时都必须重新创建。而且用完之后需要执行垃圾回收。
 *          创建对象、垃圾回收都需要付出性能的代价。
 *          而且还需要不断重复的创建、回收、创建、回收……
 *          如果是不能复用的对象，也没办法。但如果对象可以复用，那我们肯定要想办法复用。
 *          - 数据库连接：不管执行什么样的 SQL 语句，数据库连接对象都一样。
 *          - 线程对象：不管线程内执行什么样的 Java 代码，线程对象都一样。
 *          所以像这样可以重复使用的对象，通常我们都会使用『池技术』来管理起来，实现复用。
 *      [2]对象管理
 *          避免对象被无节制的创建，降低内存溢出的风险。
 *          使用池技术，可以指定创建对象的上限，很好的限制了对象的总数。
 *          阿里巴巴开发规范手册中明确、严格要求：创建线程一定要使用线程池。
 *  2、线程池内部工作原理
 *      [1]初始状态
 *          线程池对象刚刚创建出来时，里面一个线程都没有。【反直觉】
 *      [2]第一个任务到来
 *          这个任务不需要进入等待队列，线程池会创建一个线程对象来执行这个任务。
 *      [3]继续增加任务
 *          继续创建线程执行任务，直到线程数量达到了『核心线程数』。
 *      [4]继续增加任务
 *          此时存放等待中任务的阻塞队列未满，所以不创建『非核心线程』，任务进入等待队列。
 *      [5]等待队列已满
 *          此时需要创建『非核心线程』，执行最新的任务而不是等待时间最长的任务。【反直觉】
 *          A 请求：已等待5秒  体验差（即使现在处理这个请求，也没办法变成好体验）
 *          B 请求：已等待3秒  体验差
 *          C 请求：已等待2秒  体验差
 *          D 请求：刚过来    体验好（能捞一个算一个，不然这个请求体验也变差了）
 *      [6]继续增加任务
 *          线程池内，线程数量达到最大线程数，且等待队列已满——此时到达了线程池工作能力的极限。
 *          此时再增加任务就会触发拒绝策略。
 *              AbortPolicy：抛异常 java.util.concurrent.RejectedExecutionException
 *              CallerRunsPolicy：返回给调用者。例如：main线程分配的任务无法执行，就返回给main线程。
 *              DiscardOldestPolicy：抛弃等待时间最长的任务。
 *              DiscardPolicy：随机抛弃任务。
 *      [7]任务慢慢减少
 *          『非核心线程』达到最大空闲时间会被释放。
 *          『核心线程』不考察最大空闲时间，会被保留下来。
 *  3、最佳实践
 *      [1]把『核心线程数』和『最大线程数』设定为相等
 *          避免『非核心线程』创建了释放、释放了又创建
 *      [2]『最大线程数』基于 CPU 核心数量来设置
 *          比如：设置为 CPU 核心数量的两倍。
 *          最理想的状态：每一个 CPU 核心执行一个线程，没有线程之间的切换。
 *          最大负载的状态：每一个 CPU 核心执行两个线程，充分利用 CPU 资源。
 *          不合理设置：一个 CPU 核心执行的线程数量过多，线程之间切换、阻塞等等状态浪费了大量性能。
 *          举例：
 *              串行操作：100ms
 *              	第一步：20ms
 *              	第二步：50ms
 *              	第三步：30ms
 *
 *              并行操作：50ms
 *              	第一步 A线程：20ms
 *              	第二步 B线程：50ms
 *              	第三步 C线程：30ms
 *
 *              并发操作：>100ms
 *                  100ms是执行代码的时间，多出来的是线程上下文切换的时间
 *
 */
public class ThreadPoolTest {

    public static void main(String[] args) throws InterruptedException {

        // 一、使用 JDK 提供的工具类创建线程池
        // 由于该工具类中，很多地方设置参数都是使用 Integer.MAX_VALUE，有可能导致无节制创建对象，增加内存溢出的风险。
        // 所以开发时严禁使用该工具类创建线程池。
        // 1、创建线程池对象
        // ExecutorService threadPool = Executors.newFixedThreadPool(5);

        // 2、向线程池提交任务
        // threadPool.submit(()->{
        //     System.out.println(Thread.currentThread().getName() + " hello!");
        // });

        // 二、自定义线程池：我们只是指定创建线程池的参数，而不需要声明线程池的类
        // 相关 API 介绍：
        // 顶级接口：java.util.concurrent.Executor
        // 常用接口：java.util.concurrent.ExecutorService
        // 正式使用的实现类：java.util.concurrent.ThreadPoolExecutor

        // 1、准备创建线程池对象所需要的七个参数
        // 核心线程数：不检查空闲时间，不会因为超过最大空闲时间而释放。
        // 且在任务等待队列未满时，不会创建非核心线程。
        int corePoolSize = 3;

        // 最大线程数：整个线程最多可以创建几个线程
        int maximumPoolSize = 5;

        // 最大空闲时间：非核心线程达到最大空闲时间会被释放
        long keepAliveTime = 10;

        // 最大空闲时间的单位
        TimeUnit unit = TimeUnit.SECONDS;

        // 等待队列：存放等待中的任务
        // 等待队列的长度就是设定最多允许多少个任务等待
        BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(10);

        // 线程工厂：用来创建线程对象的工厂对象
        ThreadFactory threadFactory = Executors.defaultThreadFactory();

        // 拒绝策略的处理：当实际线程数已经到最大线程数，等待队列也已满，则启用拒绝策略
        RejectedExecutionHandler handler =
                new ThreadPoolExecutor.DiscardOldestPolicy();

        // 2、创建线程池对象
        ExecutorService threadPool =
                new ThreadPoolExecutor(
                    corePoolSize,
                    maximumPoolSize,
                    keepAliveTime,
                    unit,
                    workQueue,
                    threadFactory,
                    handler);

        // 3、给线程池分配任务
        while (true) {
            threadPool.submit(()->{
                try {
                    System.out.println(Thread.currentThread().getName() + " I am working!");

                    TimeUnit.MILLISECONDS.sleep(1500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });

            TimeUnit.MILLISECONDS.sleep(100);
        }
    }

}
